﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using LuaInterface;

using Microsoft.WowAddonStudio.Lua.Model;

namespace Microsoft.WowAddonStudio.Lua.Tools.XmlDoc.Converters
{
    public static class LuaConverter
    {
        private static readonly Regex signatureRegex = new Regex(@"^(?:(?<returns>[^=]+)= )?(?<type>\w+):(?<method>\w+)\((?<params>[^)]+)?\)$", RegexOptions.Compiled);
        private static readonly Regex parameterNameRegex = new Regex(@"(?<name>\w+)", RegexOptions.Compiled);

        public static Documentation ReadLuaDocumentation(string luaFile)
        {
            var lua = new LuaInterface.Lua();
            lua.DoFile(luaFile);

            var documentation = new Documentation();

            using (var globalTable = (LuaTable)lua["_G"])
            {
                foreach (string variableKey in globalTable.Keys)
                {
                    if (variableKey == "luanet")
                        continue;

                    using (var tableVariable = lua[variableKey] as LuaTable)
                    {
                        if (tableVariable == null)
                            continue;
                        try
                        {
                            // Decide whether table declares a function or table
                            Declaration declaration;

                            if (tableVariable["signature"] != null)
                                declaration = tableVariable.ToFunction();
                            else
                            {
                                try
                                {
                                    declaration = tableVariable.ToTable(variableKey);
                                }
                                catch
                                {
                                    continue;
                                }
                            }

                            if (declaration != null && !String.IsNullOrEmpty(declaration.Name))
                                Console.Write('.');

                            documentation.Add(declaration);
                        }
                        catch (LuaException)
                        {
                            Console.Write("!");
                        }
                    }
                }
            }

            Console.WriteLine();
            Console.WriteLine("{0} declarations added to XML documentation.", documentation.Declarations.Count);
            return documentation;
        }

        public static Table ToTable(this LuaTable tableTable, string tableName)
        {
            var table = new Table
                        {
                            Name = tableName,
                            Virtual = true,
                        };

            foreach (string functionKey in tableTable.Keys)
            {
                using (var functionTable = tableTable[functionKey] as LuaTable)
                {
                    if (functionTable == null)
                        continue;

                    var function = functionTable.ToFunction();

                    if (!String.IsNullOrEmpty(function.Name))
                        table.Declarations.Add(function);
                }
            }

            return table;

        }

        public static Function ToFunction(this LuaTable functionTable)
        {
            var function = new Function
                       {
                           Name = functionTable.GetString("name"),
                           Summary = functionTable.GetString("desc"),
                           Parameters = GetParameterDeclarations(functionTable, "arguments"),
                           Returns = GetApiDeclarations<ReturnValue>(functionTable, "returns")
                       };

            if (function.Parameters == null && function.Returns == null && functionTable.GetString("signature") != null)
                ParseSignature(function, functionTable.GetString("signature"));

            return function;
        }

        private static void ParseSignature(Function function, string signature)
        {
            var match = signatureRegex.Match(signature);
            while (match.Success)
            {
                var parameters = match.Groups["params"];
                var returns = match.Groups["returns"];

                if (parameters.Success)
                {
                    string[] parameterNames = parameters.Value.Replace("[", String.Empty).Replace("]", String.Empty).Split(',');
                    function.Parameters = parameterNames.Select(name => new Parameter {Name = name.Replace("\"", String.Empty).Trim()}).ToArray();
                }

                if (returns.Success)
                {
                    string[] returnNames = returns.Value.Split(',');
                    function.Returns = returnNames.Select(name => new ReturnValue {Name = name.Trim()}).ToArray();
                }

                match = match.NextMatch();
            }

            Console.WriteLine(signature);
        }

        private static Parameter[] GetParameterDeclarations(LuaTable functionTable, string tableName)
        {
            using (var apiDeclarationsTable = (LuaTable)functionTable[tableName])
            {
                if (apiDeclarationsTable == null)
                    return null;

                foreach (var apiDeclarationKey in apiDeclarationsTable.Keys)
                {
                    using (var apiDeclarationTable = apiDeclarationsTable[apiDeclarationKey] as LuaTable)
                    {
                        if (apiDeclarationTable == null)
                            continue;

                        if ((string)apiDeclarationTable["group"] == "choice")
                        {
                            return new[]
                                   {
                                       new ParameterChoice
                                       {
                                           Choices = GetApiDeclarations<Parameter>(apiDeclarationTable, "choices")
                                       }
                                   };
                        }
                    }
                }
            }

            return GetApiDeclarations<Parameter>(functionTable, tableName);
        }


        private static TApiDeclaration[] GetApiDeclarations<TApiDeclaration>(LuaTable functionTable, string tableName) where TApiDeclaration : ApiDeclaration, new()
        {
            using (var apiDeclarationsTable = (LuaTable)functionTable[tableName])
            {
                if (apiDeclarationsTable != null)
                {
                    var apiDeclarations = new List<TApiDeclaration>();
                    foreach (var apiDeclarationKey in apiDeclarationsTable.Keys)
                    {
                        using (var apiDeclarationTable = apiDeclarationsTable[apiDeclarationKey] as LuaTable)
                        {
                            if (apiDeclarationTable == null)
                                continue;

                            var apiDeclaration = ToApiDeclaration<TApiDeclaration>(apiDeclarationTable);

                            if (apiDeclaration is Parameter)
                                (apiDeclaration as Parameter).Optional = (bool?)apiDeclarationTable["optional"] ?? false;

                            apiDeclarations.Add(apiDeclaration);
                        }
                    }

                    return apiDeclarations.Count > 0 ? apiDeclarations.ToArray() : null;
                }
            }

            return null;
        }

        public static TApiDeclaration ToApiDeclaration<TApiDeclaration>(this LuaTable table) where TApiDeclaration : ApiDeclaration, new()
        {
            var apiDeclaration = new TApiDeclaration
                   {
                       ApiType = table.GetString("type").ToApiType(),
                       Name = table.GetString("name"),
                       Summary = table.GetString("desc")
                   };

            if (apiDeclaration.ApiType == ApiType.Unknown)
                apiDeclaration.Type = table.GetString("type").ToLuaType();

            return apiDeclaration;
        }

        public static string GetString(this LuaTable table, string key)
        {
            var value = (string)table[key];

            return String.IsNullOrEmpty(value) ? null : value;
        }

        public static ApiType ToApiType(this string value)
        {
            if (!String.IsNullOrEmpty(value))
            {
                try
                {
                    return (ApiType)Enum.Parse(typeof(ApiType), value, true);
                }
                catch (ArgumentException)
                {
                }

                switch (value)
                {
                    case "1nil":
                        return ApiType.OneNil;
                }
            }

            return ApiType.Unknown;
        }

        public static LuaType ToLuaType(this string value)
        {
            if (!String.IsNullOrEmpty(value))
            {
                try
                {
                    return (LuaType)Enum.Parse(typeof(LuaType), value, true);
                }
                catch (ArgumentException)
                {
                }
            }

            return LuaType.Unknown;
        }
    }
}